package top.jle.spring.basic.config;

import io.swagger.v3.core.util.AnnotationsUtils;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.SpecVersion;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.parameters.HeaderParameter;
import lombok.NonNull;
import org.springdoc.core.configuration.SpringDocConfiguration;
import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.customizers.RouterOperationCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.utils.PropertyResolverUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.*;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import top.jle.spring.basic.annotation.EnableOpenAPI;

import java.util.Locale;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED;


@Configuration(proxyBeanMethods = false)
@Import({SpringDocConfiguration.class})
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
@Order(-1)
public class OpenAPIConfig implements ImportAware {

    protected EnableOpenAPI enableOpenAPI;
    @Autowired
    PropertyResolverUtils propertyResolverUtils;


    @Value("${auth.token-header:AUTH-TOKEN}")
    private String authTokenHeader;
    @Value("${server.servlet.context-path:}")
    private String contextPath;

    @Override
    public void setImportMetadata(@NonNull AnnotationMetadata importMetadata) {
        MergedAnnotation<EnableOpenAPI> annotation = importMetadata.getAnnotations().get(EnableOpenAPI.class);
        this.enableOpenAPI = annotation.synthesize();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public OpenAPI restfulOpenAPI() {

        //配置认证、请求头参数
        Components components = new Components();
        components.addParameters("globalHeader", new HeaderParameter().required(true).name(authTokenHeader).description("").schema(new StringSchema()).required(true));
        OpenAPI openAPI = new OpenAPI(SpecVersion.V31).components(components);
        buildOpenAPIWithOpenAPIDefinition(openAPI, Locale.getDefault());
        return openAPI;
    }

    @Bean
    public OpenApiCustomizer customerGlobalHeaderOpenApiCustomizer() {
        return openApi -> {
            Paths paths = openApi.getPaths();
            //添加全局的请求头参数
            paths.values().stream().flatMap(pathItem -> pathItem.readOperations().stream())
                    .forEach(operation -> operation.addParametersItem(new HeaderParameter().$ref("#/components/parameters/globalHeader")));
        };
    }

    private void buildOpenAPIWithOpenAPIDefinition(OpenAPI openAPI, Locale locale) {
        // info
        Assert.isTrue(this.enableOpenAPI!=null,"openApiConfig is null");
        AnnotationsUtils.getInfo(this.enableOpenAPI.info()).map(info -> resolveProperties(info, locale)).ifPresent(openAPI::setInfo);
        // OpenApiDefinition external docs
        AnnotationsUtils.getExternalDocumentation(this.enableOpenAPI.externalDocs()).ifPresent(openAPI::setExternalDocs);


    }

    private Info resolveProperties(Info info, Locale locale) {
        resolveProperty(info::getTitle, info::title, propertyResolverUtils, locale);
        resolveProperty(info::getDescription, info::description, propertyResolverUtils, locale);
        resolveProperty(info::getVersion, info::version, propertyResolverUtils, locale);
        resolveProperty(info::getTermsOfService, info::termsOfService, propertyResolverUtils, locale);

        License license = info.getLicense();
        if (license != null) {
            resolveProperty(license::getName, license::name, propertyResolverUtils, locale);
            resolveProperty(license::getUrl, license::url, propertyResolverUtils, locale);
        }

        Contact contact = info.getContact();
        if (contact != null) {
            resolveProperty(contact::getName, contact::name, propertyResolverUtils, locale);
            resolveProperty(contact::getEmail, contact::email, propertyResolverUtils, locale);
            resolveProperty(contact::getUrl, contact::url, propertyResolverUtils, locale);
        }
        return info;
    }

    private void resolveProperty(Supplier<String> getProperty, Consumer<String> setProperty,
                                 PropertyResolverUtils propertyResolverUtils, Locale locale) {
        String value = getProperty.get();
        if (org.apache.commons.lang3.StringUtils.isNotBlank(value)) {
            setProperty.accept(propertyResolverUtils.resolve(value, locale));
        }
    }
    /*
     * 下面两个bean需要结合使用 前者是在接口地址中拼接contextPath前缀  后者是在baseUrl中取消contextPath
     * 由于OpenAPI3的UI提交请求时 是使用ServerBaseUrl+接口Path进行请求故需要使用以下两个Customizer完成操作
     * @date  2023/6/5 下午6:28
     * @author  jim.lee
     */

    /**
     * <p>由于Openapi3的api-docs接口中的path不会拼接contextPath,故需要使用customizer完成拼接</p>
     * <p>否则导入API-FOX后无法正常请求</p>
     *
     * @date 2023/6/5 下午6:31
     * @author jim.lee
     */
    @Bean
    public RouterOperationCustomizer routerOperationCustomizer() {
        return (routerOperation, handlerMethod) -> {
            //在所有接口地址上增加prefix
            routerOperation.setPath(enableOpenAPI.prefix() + routerOperation.getPath());
            return routerOperation;
        };
    }

    @Bean
    public ServerBaseUrlCustomizer serverBaseUrlCustomizer() {
        //在serverBaseUrl中取消contextPath
        return serverBaseUrl -> StringUtils.hasText(contextPath) ? serverBaseUrl.substring(0, serverBaseUrl.indexOf(contextPath)) : serverBaseUrl;
    }

}
